﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using TowerDefence.LanguageExtensions;

namespace TowerDefence
{
	/// <summary>
	/// Struct used to represent a line in the standard form C = Ax + By.
	/// </summary>
	public struct LinearEquation
	{
		// line equation parameters
		public readonly float A;
		public readonly float B;
		public readonly float C;

		/// <summary>
		/// Creates a new instance of the LinearEquation struct.
		/// </summary>
		/// <param name="point1">
		/// First point on the line.
		/// </param>
		/// <param name="point2">
		/// Second point on the line.
		/// </param>
		public LinearEquation(ref Vector2 point1, ref Vector2 point2)
		{
			this.A = point2.Y - point1.Y;
			this.B = point1.X - point2.X;

			// Plug values from point1 and parameters above into C= Ax + By to find C.
			this.C = (this.A * point1.X) + (this.B * point1.Y);
		}



		/// <summary>
		/// Compares this linear equation with another instance of a LinearEquation to
		/// test whether they intersect (if not, they are parallel).
		/// </summary>
		/// <param name="line">
		/// Linear equation to test against this instance.
		/// </param>
		/// <returns>
		/// True if intersection, false if no intersection.
		/// </returns>
		public bool IntersectsLine(ref LinearEquation line)
		{
			return (LinearEquation.CalculateDivisor(ref this, ref line) != 0);
		}



		/// <summary>
		/// Checks if the provided point lies on the line defined by this linear
		/// equation.
		/// </summary>
		/// <param name="point">
		/// Point to test against the line.
		/// </param>
		/// <returns>
		/// True if line passes through point, false if it does not.
		/// </returns>
		public bool IntersectsPoint(ref Vector2 point)
		{
			// Compute the C value for the given point based on this equation's
			// values for A and B parameters and compare with this equation's C.

			return (((this.A * point.X) + (this.B * point.Y)) == this.C);
		}



		/// <summary>
		/// Checks if the line passes through the circle defined by the provided
		/// parameters. Note that the line is assumed to have infinite length.
		/// </summary>
		/// <param name="origin">
		/// Origin of the circle being tested.
		/// </param>
		/// <param name="radius">
		/// Radius of the circle being tested.
		/// </param>
		/// <returns>
		/// True if line passes through the circle, false if it does not.
		/// </returns>
		public bool IntersectsCircle(ref Vector2 origin, float radius)
		{
			// First, check if the circle origin lies on this line. If it does, short
			// circuit the method, as the line is guaranteed to intercept the circle.

			// IF circle origin is coincident with the line:
			if (this.IntersectsPoint(ref origin))
			{
				return true;
			}
			// ELSE line and circle are not coincident:
			else
			{
				// To find if the line passes through the circle, it is necessary to
				// project an imaginary line from the circle's origin perpendicular
				// to the angle of the line it is being tested against. This is because
				// a perpendicular line is guaranteed to give an intercept. If the
				// point of intersection is further away from the circle's origin than
				// the length of the circle's radius, then the line and circle do not intersect.

				// Find the angle of the line. To do this, find the X- and Y-intercept of this 
				// linear equation and use them to form a right triangle. Since they are intercepts,
				// the other coordinate component is always zero.

				// X intercept, set Y to 0 and solve for X, i.e. x = C/A.
				float x = this.C / this.A;

				// Y intercept, set X to 0 and solve for Y i.e. y = C/B.
				float y = this.C / this.B;

				// Calculate the angle between (x, 0) and (0, y), plus perpendicular offset.
				float angle = (float)Math.Atan2(-y, x) + MathHelper.PiOver2;

				// Project a line from the circle origin at this angle to get the end point
				// of the perpendicular radius.
				Vector2 point = origin.ProjectLine(radius, angle);

				// Find the equation of the line from origin to radial point.
				var radialLine = new LinearEquation(ref origin, ref point);

				// IF the radial line intersects with the line it is being tested against:
				if (this.IntersectsLine(ref radialLine))
				{
					// Find the point of intersection.
					point = LinearEquation.ComputeInterceptPoint(ref this, ref radialLine);

					// Calculate the distance between the point of intersection and the circle's
					// origin and compare this to the radius of the circle.
					
					// IF calculated distance is within the radius of the circle:
					if (Vector2.Distance(origin, point) <= radius)
					{
						// Line intersects circle.
						return true;
					}
					// ELSE the line does not pass through the circle:
					else
					{
						return false;
					}
					// ENDIF
				}
				// ELSE Something must have gone wrong:
				else
				{
					return false;
				}
				// ENDIF
			}
			// ENDIF
		}

		/// <summary>
		/// Finds the point of intersection between two straight line equations in
		/// standard form.
		/// </summary>
		/// <param name="line1">
		/// Standard form equation of the first line.
		/// </param>
		/// <param name="line2">
		/// Standard form equation of the second line.
		/// </param>
		/// <returns>
		/// Point of intersection.
		/// </returns>
		static public Vector2 ComputeInterceptPoint(ref LinearEquation line1, ref LinearEquation line2)
		{
			float divisor = LinearEquation.CalculateDivisor(ref line1, ref line2);

			// Even though a test should be done before getting here, there is
			// no way to guarantee that parallel lines will not be fed into
			// this method, so if the divisor indicates parallelism, make it a
			// very small number instead, which will produce very large numbers 
			// for x and y.

			if (divisor == 0)
			{
				divisor = 0.0001f;
			}

			// x = b2*c1 - b1*c2 / d
			float x = ((line2.B * line1.C) - (line1.B * line2.C)) / divisor;
			// y = a1*c2 - a2*c1 / d
			float y = ((line1.A * line2.C) - (line2.A * line1.C)) / divisor;

			return new Vector2(x, y);
		}



		/// <summary>
		/// Utility method for calculating the divisor of two C = Ax + By linear
		/// equations for intersection calculations.
		/// </summary>
		/// <param name="line1">
		/// First line equation.
		/// </param>
		/// <param name="line2">
		/// Second line equation.
		/// </param>
		/// <returns>
		/// Divisor.
		/// </returns>
		static private float CalculateDivisor(ref LinearEquation line1, ref LinearEquation line2)
		{
			// d = a1*b2 - a2*b1
			return (line1.A * line2.B) - (line2.A * line1.B);
		}
	}
}
